package com.stox.client.ui.javafx.chartingy.model.plot;

import java.util.ArrayList;
import java.util.List;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;

import com.stox.client.ui.javafx.chartingy.model.axis.DateAxis;
import com.stox.client.ui.javafx.chartingy.model.chart.Chart;
import com.stox.client.ui.javafx.chartingy.model.chart.PrimaryChart;
import com.stox.client.ui.javafx.chartingy.model.enumeration.Overlay;
import com.stox.client.ui.javafx.chartingy.model.node.DataNode;
import com.stox.client.ui.javafx.chartingy.model.node.PlotNode;
import com.stox.client.ui.javafx.chartingy.view.ChartingView;
import com.stox.client.ui.javafx.chartingy.widget.PlotInfoPanel;
import com.stox.shared.core.model.Bar;
import com.stox.shared.core.model.Scrip;

public abstract class Plot<T, V> extends Group {

	private PlotNode<T, V> plotNode;
	private Chart<T, V> chart;
	private final ChartingView chartingView;
	private double max = Double.MIN_VALUE;
	private double min = Double.MAX_VALUE;
	protected double backingDataMax = Double.MIN_VALUE;
	protected double backingDataMin = Double.MAX_VALUE;
	protected double previousBackingDataMax = Double.MIN_VALUE;
	protected double previousBackingDataMin = Double.MAX_VALUE;
	private final IntegerProperty indexProperty = new SimpleIntegerProperty(0);
	private final ObjectProperty<Color> colorProperty = new SimpleObjectProperty<Color>(Color.ORANGE);
	private final StringProperty displayNameProperty = new SimpleStringProperty("");
	private final ObjectProperty<T> extraValueProperty = new SimpleObjectProperty<T>();
	private final ListProperty<Data<T, V>> dataProperty = new SimpleListProperty<Data<T, V>>(FXCollections.observableArrayList());
	private final ObjectProperty<Overlay> overlayProperty = new SimpleObjectProperty<>(Overlay.PRICE);
	private final PlotInfoPanel plotInfoPanel;

	public Plot(ChartingView chartingView) {
		this.chartingView = chartingView;
		plotInfoPanel = new PlotInfoPanel(this);
		dataProperty.addListener(new ListChangeListener<Data<T, V>>() {
			@Override
			public void onChanged(final javafx.collections.ListChangeListener.Change<? extends Data<T, V>> change) {
				while (change.next()) {
					if (change.wasAdded()) {
						associateDataNodes(change.getAddedSubList(), true);
					}
					if (change.wasRemoved()) {
						associateDataNodes(change.getRemoved(), false);
					}
				}
			}
		});
	}

	private void associateDataNodes(final List<? extends Data<T, V>> datas, final boolean add) {
		if ((null == datas) || datas.isEmpty() || (null == chart)) {
			return;
		}
		final List<Node> nodes = new ArrayList<Node>(datas.size());
		if (add) {
			updateDataNodes(datas);
		}
		for (final Data<T, V> data : datas) {
			if (null != data.getDataNode()) {
				nodes.add(data.getDataNode());
			}
		}
		if (add) {
			getChildren().addAll(nodes);
		} else {
			getChildren().removeAll(nodes);
		}
	}

	public void renewDataNodes() {
		associateDataNodes(dataProperty, false);
		associateDataNodes(dataProperty, true);
	}

	public abstract void load();

	public abstract void updateDataNodes(List<? extends Data<T, V>> datas);

	public void updateAxisBounds() {
		max = Double.MIN_VALUE;
		min = Double.MAX_VALUE;
		final DateAxis dateAxis = primaryChart().getDateAxis();
		final int lowerBoundIndex = dateAxis.getLowerBoundIndex();
		final int upperBoundIndex = dateAxis.getUpperBoundIndex();
		for (int index = lowerBoundIndex; index >= upperBoundIndex; index--) {
			if (index < dataProperty.getSize()) {
				final Data<T, V> data = dataProperty.get(index);
				max = Math.max(max, data.getMax());
				min = Math.min(min, data.getMin());
			}
		}
		chart.setMin(Math.min(chart.getMin(), min));
		chart.setMax(Math.max(chart.getMax(), max));
	}

	@Override
	protected void layoutChildren() {
		/*
		 * No Op
		 */
	}

	private int lastLowerBoundIndex = 0;
	private int lastUpperBoundIndex = 0;

	public void layoutChartChildren() {
		final DateAxis dateAxis = primaryChart().getDateAxis();
		final int lowerBoundIndex = dateAxis.getLowerBoundIndex();
		final int upperBoundIndex = dateAxis.getUpperBoundIndex();
		final double unitWidht = dateAxis.getUnitWidth();
		lastLowerBoundIndex = Math.max(lastLowerBoundIndex, lowerBoundIndex);
		lastUpperBoundIndex = Math.min(lastUpperBoundIndex, upperBoundIndex);

		if (null != getPlotNode()) {
			getPlotNode().preLayout();
		}

		for (int index = lastLowerBoundIndex; index >= lastUpperBoundIndex; index--) {
			if ((0 <= index) && (dataProperty.getSize() > index)) {
				final Data<T, V> data = dataProperty.get(index);
				double x = 0;
				if ((index <= lowerBoundIndex) && (index >= upperBoundIndex)) {
					final DataNode<Scrip, Bar> primaryDataNode = primaryPlot().getData().get(data.getIndex()).getDataNode();
					if ((null != primaryDataNode) && primaryDataNode.isVisible()) {
						x = primaryDataNode.getRectangle().getX();
						if ((null != data.getDataNode()) && (null != primaryDataNode)) {
							data.getDataNode().setVisible(true);
							data.getDataNode().getRectangle().setX(x);
							data.getDataNode().getRectangle().setWidth(primaryDataNode.getRectangle().getWidth());
							data.getDataNode().layoutChartChildren();
						}
						if (null != getPlotNode()) {
							getPlotNode().layout(data, x, unitWidht);
						}
					}
				} else {
					if (null != data.getDataNode()) {
						data.getDataNode().setVisible(false);
					}
				}
			}
		}
		if (null != getPlotNode()) {
			getPlotNode().postLayout();
		}
		lastLowerBoundIndex = lowerBoundIndex;
		lastUpperBoundIndex = upperBoundIndex;
	}

	public ObjectProperty<Overlay> overlayProperty() {
		return overlayProperty;
	}

	public Overlay getOverlay() {
		return overlayProperty.get();
	}

	public void setOverlay(final Overlay overlay) {
		overlayProperty.set(overlay);
	}

	public ObservableList<Data<T, V>> getData() {
		return dataProperty;
	}

	public void setData(final ObservableList<Data<T, V>> data) {
		dataProperty.set(data);
	}

	public ListProperty<Data<T, V>> dataProperty() {
		return dataProperty;
	}

	public T getExtraValue() {
		return extraValueProperty.get();
	}

	public void setExtraValue(final T extraValue) {
		extraValueProperty.set(extraValue);
	}

	public ObjectProperty<T> extraValueProperty() {
		return extraValueProperty;
	}

	public Color getColor() {
		return colorProperty.get();
	}

	public void setColor(final Color color) {
		colorProperty.set(color);
	}

	public ObjectProperty<Color> colorProperty() {
		return colorProperty;
	}

	public String getDisplayName() {
		return displayNameProperty.get();
	}

	public StringProperty displayNameProperty() {
		return displayNameProperty;
	}

	public int getIndex() {
		return indexProperty.get();
	}

	public void setIndex(final int index) {
		indexProperty.set(index);
	}

	public IntegerProperty indexProperty() {
		return indexProperty;
	}

	public PlotNode<T, V> getPlotNode() {
		return plotNode;
	}

	public void setPlotNode(final PlotNode<T, V> plotNode) {
		if (null != this.plotNode) {
			getChildren().remove(this.plotNode);
			plotInfoPanel.getDisplayNameLabel().effectProperty().unbind();
		}
		this.plotNode = plotNode;
		if (null != plotNode) {
			plotInfoPanel.getDisplayNameLabel().effectProperty().bind(plotNode.effectProperty());
			getChildren().add(plotNode);
		}
	}

	public Chart<T, V> getChart() {
		return chart;
	}

	public void setChart(final Chart<T, V> chart) {
		this.chart = chart;
	}

	public double getMax() {
		return max;
	}

	protected void setMax(final double max) {
		this.max = max;
	}

	public double getMin() {
		return min;
	}

	protected void setMin(final double min) {
		this.min = min;
	}

	public PlotInfoPanel getPlotInfoPanel() {
		return plotInfoPanel;
	}

	/**
	 * Utility method
	 *
	 * @return
	 */

	public ChartingView chartingView() {
		return chartingView;
	}

	public PrimaryChart primaryChart() {
		return chartingView.getPrimaryChart();
	}

	public PricePlot primaryPlot() {
		return primaryChart().getPrimaryPlot();
	}

	public boolean isPrimary() {
		return equals(chart.getPrimaryPlot());
	}

	public double getBackingDataMax() {
		return backingDataMax;
	}

	public double getBackingDataMin() {
		return backingDataMin;
	}
}
